package adke.gui.panels;

import adke.Util;
import adke.listeners.GameListener;
import adke.managers.GameManager;
import adke.models.LinePanelBorder;
import adke.gui.Overview;
import adke.managers.PlayerManager;
import adke.models.Player;
import adke.models.powerups.Powerup;
import java.awt.BasicStroke;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;

// hier wordt op getekend
// de klasse 

public class LinePanel extends JPanel implements GameListener
{
    // hier tekenen we op
    private BufferedImage drawer;
    
    // een klein deel van de buffer die we repainten
    private BufferedImage repaintRegion;
    
    private Graphics drawerGraphics;
     
    // dit is de manager van de border
    private static LinePanelBorder border;
    
    private static LinePanel reference;
    
    // deze waardne hebben we nodig om een bufferedimage te maken
    public LinePanel(int width, int height)
    {
        reference = this;
        
        GameManager.addListener(this);
        
        drawer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        drawer = Util.optimizeImage(drawer);
        drawer.setAccelerationPriority(1);
        drawerGraphics = drawer.getGraphics();
        
        // instantieer de border
        border = new LinePanelBorder();
        
        // als de border niet ge enabled is zal er niks getekend worden
        drawBorder();
        
        // het GFX paneel moet precies op deze geplaatst worden
        // anders komen er artefacten bij het tekenen
        setLayout(new GridLayout());
        add(new GFXPanel2(width, height));
    }
    
    public static LinePanel getInstance()
    {
        return reference;
    }
    
    public static LinePanelBorder getLinePanelBorder()
    {
        return border;
    }
    
    public void paintComponent(Graphics g)
    {
        // TODO: alleen painten over regionen die veranderd zijn
        g.drawImage(drawer, 0, 0, null);
    }
    
    // als we dit in paint zetten kunnen er paints worden overgeslagen
    // deze moet dus aangeroepen worden náást pain
    // dit kunnen we tevens niet in paint zetten omdat de startpunten
    // van spelers dan al getekend worden zonder dat we dat zelf aangegeven hebben.
    // doordat het paneel een eerste keer getekend moet worden namelijk.
    public Rectangle[] paintPlayersTimeSafe()
    {
        if (drawerGraphics == null) return new Rectangle[0];
        return PlayerManager.paintPlayers(drawerGraphics);
    }
    
    public void repaint()
    {
        Rectangle[] changes = paintPlayersTimeSafe();
        for (Rectangle r : changes)
            super.repaint(r);
    }
    
    // repaint zonder de posities van spelers op de buffer te schirjven
    public void cleanRepaint()
    {
        // paintcomponent dus
        super.repaint();
    }
    
    // repaint alleen het kleine stukje van de buffer dat is aangegeven
    public void paintRegion(Rectangle region)
    {
        // maak een image uit de rect die we krijgen
        repaintRegion = drawer.getSubimage((int)region.getX(), (int)region.getY(), 
                (int)region.getWidth(), (int)region.getHeight());
        
        // verkrijg de graphics van het paneel en teken de image op de goede plek
        getGraphics().drawImage(repaintRegion, (int)region.getX(), (int)region.getY(), null);
    }
    
    // nieuwe ronde, nieuwe kansen
    public void reset()
    {
        drawerGraphics.clearRect(0, 0, drawer.getWidth(), drawer.getHeight());
        border.toDefault();
        
        // drawborder doet cleanrepaint
        drawBorder();
    }
    
    // teken de border opnieuw
    // haalt de oude weg
    public void drawBorder()
    {
        border.repaint();
    }
    
    public Graphics getPanelGraphics()
    {
        return drawerGraphics;
    }
    
    public BufferedImage getBuffer()
    {
        return drawer;
    }
    
    // kijk of we niet botsen tegen een muur of slang/lijn
    public boolean headingToEmptySpace(Point p, final int radius, final double direction, double speed) // p = slang kop, radius = linethickness
    {
        Point[] headingPoint = getHeadingPoint(p, radius, direction, speed);
        return headingToEmptySpace(headingPoint);
    }

    // een point[] is zijn dus de drie punten vóór een slang, in zijn gezichtsveld
    
    public boolean headingToEmptySpace(Point[] headingPoint)
    {
        boolean allBlack = true;
        for (Point point : headingPoint)
        {
            Color c = getColorInField(point.x, point.y);
            if (c == null || !c.equals(Color.BLACK))
            {
                allBlack = false;
                break;
            }
        }
        return allBlack;
    }
    
    // verkrijg de drie punten waar we naar kijken als we een botsing proberen te determineren
    // [0] = linkerpunt, [1] = midden, [2] = rechter
    public Point[] getHeadingPoint(Point p, final int radius, final double direction, double speed)
    {
        Point[] headingPoint = new Point[3];
        
        // zie de paint van Line.
        // de x en y zijn een benadering van het middelpunt van de nieuwste ovaal
        double x = p.getX();
        double y = p.getY();
        
        // we hebben een lijn en we doen net of het een slang is.
        // we gaan hier net doen of aan de twee zijkanten van de kop twee ogen
        // zitten die vooruit kijken. zo zien we precies of er recht voor
        // de slang een leeg punt is, of niet.
        
        // de dx en dy om vanaf het een gegeven punt *radius + 2* pixels in de geven hoek 
        // verder te komen
        double[] dxdy = Util.calculateNewPointDiff(direction, radius); // het tweede getal is een beetje speling
        
        // de coordinaten van het middelste punt vóór de kop van de slang
        double xView = x + dxdy[0];
        double yView = y + dxdy[1];
        
        headingPoint[1] = new Point((int)xView, (int)yView);
        
        // nu kijken we naar de twee hoeken, want het middelste punt hebben we al gehad.
        // hiervoor maken we vanaf het nieuwe viewpunt twee keer een bocht van 90 graden
        // en stappen we *radius*pix verder
        
        // eerst kijken we naar het rechterpunt (tov het denkbeeldige middelpunt)
        dxdy = Util.calculateNewPointDiff(direction + 90, radius/2);
        
        // de coordinaten van het rechterhoekpunt, evenwijdig aan de rechterzijkant van de lijn
        // maar dan een aantal pixels verder in de tijd
        double xCornerR = xView + dxdy[0];
        double yCornerR = yView + dxdy[1];
        
        headingPoint[2] = new Point((int)xCornerR, (int)yCornerR);
        
        // en nu het linkerpunt (tov van het zelfde middelpunt)
        dxdy = Util.calculateNewPointDiff(direction - 90, radius/2);
        
        // de coordinaten van het linkerhoekpunt, evenwijdig aan de rechterzijkant van de lijn
        // maar dan een aantal pixels verder in de tijd
        double xCornerL = xView + dxdy[0];
        double yCornerL = yView + dxdy[1];
        
        headingPoint[0] = new Point((int)xCornerL, (int)yCornerL);
        
        return headingPoint;
    }
    
    public Color getColorInField(int x, int y)
    {
        try
        {
            return new Color(drawer.getRGB(x, y));
        }
        catch (ArrayIndexOutOfBoundsException e)
        {
            return null;
        }
    }
    
    public Color getColorInField(Point p)
    {
        return getColorInField(p.x, p.y);
    }
    
    // geef de kleur die voorkomt in dit gezichtsveld
    public Color getColorInField(Point[] p)
    {
        // kijk eerst of dit gezichtsveld helemaal zwart is
        if (headingToEmptySpace(p))
            return Color.BLACK;
        
        // blijkbaar zit er de kleur van een speler of van de muur tussen
        for (Point point : p)
        {
            Color pointC = getColorInField(point);
            if (pointC == null || !pointC.equals(Color.BLACK))
                return pointC;
        }
        
        // dit hoort nooit te gebeuren
        return null;
    }
    
    public boolean pointIsInField(Point p)
    {
        int w = drawer.getWidth();
        int h = drawer.getHeight();
        int x = (int)p.getX();
        int y = (int)p.getY();
        
        // als de x en y binnen de afbakening vallen
        return (x > 0 && x < w &&
                y > 0 && y < h);
    }
    
    // is dit een open plek om een powerup te droppen?
    public boolean validateDropSpot(int x, int y)
    {
        int sampleSize = Powerup.IMG_DIMENSION / 10;
        
        // maak in de rectangle ter grootte van een powerup een raster
        // hieruit nemen we samples om te kijken of dit hok wel helemaal zwart is
        for (int x2 = 0; x2 + sampleSize <= Powerup.IMG_DIMENSION; x2 = x2 + sampleSize)
        {
            for (int y2 = 0; y2 + sampleSize <= Powerup.IMG_DIMENSION; y2 = y2 + sampleSize)
            {
                Color c = getColorInField(x + x2, y + y2);
                if (c == null || !c.equals(Color.BLACK))
                    return false;
            }
        }
        
        return true;
    }
    
    // TODO
    public Point getPlayerFavoredDropSpot(Player player)
    {
        return null;
    }

    // LISTENER
    
    public void gameStarted()
    {
    }

    public void gameEnded()
    {
    }

    public void roundStarted()
    {
        reset();
    }

    public void roundEnded()
    {
    }
}
